package lt.mockos.app.client.cpu.hw;

import java.util.LinkedList;
import java.util.Queue;

import lt.mockos.app.client.cpu.hw.Memory.Segment;
import lt.mockos.app.client.cpu.hw.descriptor.ProcessDescriptor;
import lt.mockos.app.client.cpu.hw.descriptor.ResourceDescriptor.Channel;
import lt.mockos.app.client.cpu.hw.type.ProcessPriority;
import lt.mockos.app.client.cpu.hw.type.ProcessStatus;
import lt.mockos.app.client.cpu.hw.type.ProcessType;
import lt.mockos.app.client.cpu.hw.type.Registers;

import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.Timer;

public abstract class RealMachine {

    private Registers registers;
    private Memory memory;
    private SuperMemory supMemory;
    private Interpreter interpreter;

    private ProcessesManager processesManager;
    private ResourcesManager resourcesManager;
    public Queue<ProcessDescriptor> processesQueue = new LinkedList<ProcessDescriptor>();
    
    public static final int EXECUTOR_WAIT_TIME = 300;
    
    private Timer executor = new Timer() {
        
        @Override
        public void run() {
            doStep();
        }
    };

    public Timer getExecutor(){
        return executor;
    }
    
    public SuperMemory getSuperMemory() {
        return supMemory;
    }

    public Memory getMemory() {
        return memory;
    }

    public ProcessDescriptor loader(String code, String vmTitle) {

        registers = new Registers();
        getMemory().allocateVMMemory(registers);
        getMemory().loadTask(registers.PTR, code);

        ProcessDescriptor pdVM = processesManager.createProcess(vmTitle, (ProcessDescriptor) getSuperMemory().getDescriptorByTitle("Loader"), ProcessPriority.USER, ProcessType.VM);
        processesManager.startProcess(pdVM);
        pdVM.setSavedRegisters(registers);
        pdVM.setStatus(ProcessStatus.STOP);
        return pdVM;
    }

    private void startStop() {
        ProcessDescriptor pd = processesManager.createProcess("StartStop", null, ProcessPriority.SUPER, ProcessType.STARTSTOP);
        processesManager.startProcess(pd);

        resourcesManager.createResource("Input", pd, Channel.INPUT);

        pd = processesManager.createProcess("Loader", pd, ProcessPriority.SUPER, ProcessType.LOADER);
        processesManager.startProcess(pd);
        
        executor.scheduleRepeating(EXECUTOR_WAIT_TIME);
    }

    private EventBus eventBus;

    public RealMachine(EventBus eventBus) {
        this.eventBus = eventBus;
    }

    public void init() {
        memory = new Memory();
        supMemory = new SuperMemory(eventBus);

        resourcesManager = new ResourcesManager(supMemory);
        processesManager = new ProcessesManager(supMemory, resourcesManager);

        startStop();
        registers = new Registers();
        interpreter = new Interpreter(this, eventBus);
    }

    public void callRun() {
//        processesQueue.clear();
        
        interpreter.callRun();
    }

    public void callStep() {
        interpreter.callStep();
    }

    public void setRegisters(Registers registers) {
        this.registers = registers;
    }

    public Registers getRegisters() {
        return registers;
    }

    private void increaseCP() {
        registers.CP++;
    }

    private void increaseSP() {
        registers.SP++;
    }

    private void decreaseSP() {
        registers.SP--;
    }

    private void decreaseTIME() {
        registers.TIME--;
        if (registers.TIME <= 0)
            registers.TI = 1;
    }

    public String getCodeWord() {
        if (registers.TIME <= 0)
            registers.TI = 1;
        else
            registers.TI = 0;

        String codeWord = memory.readWord(registers.PTR, registers.CP, Memory.Segment.Segment_Code);
        increaseCP();
        decreaseTIME();

        return codeWord;
    }

    public void ADD() {
        registers.AR += registers.BR;
    }

    public void SUB() {
        registers.AR -= registers.BR;
    }

    public void MUL() {
        registers.AR *= registers.BR;
    }

    public void DIV() {
        registers.AR /= registers.BR; // division by zero interrupt
    }

    public void NEG() {
        registers.AR = 0 - registers.AR;
    }

    public void CMP() {
        if (registers.AR > registers.BR)
            registers.SF = 0;
        else if (registers.AR < registers.BR)
            registers.SF = 2;
        else
            registers.SF = 1;
    }

    public void JT(int x, int y) {
        if (!memory.isValidAddress(registers.PTR, x, y, Segment.Segment_Code)) {
            registers.PI = 1;
            return;
        }

        registers.CP = memory.getAddress(x, y);
    }

    public void JH(int x, int y) {
        if (registers.SF == 0)
            this.JT(x, y);
    }

    public void JE(int x, int y) {
        if (registers.SF == 1)
            this.JT(x, y);
    }

    public void JL(int x, int y) {
        if (registers.SF == 2)
            this.JT(x, y);
    }

    public void HALT() {
        registers.SI = 1;
    }

    public void LW(int x, int y) {
        if (!memory.isValidAddress(registers.PTR, x, y, Segment.Segment_Data)) {
            registers.PI = 1;
            return;
        }

        registers.AR = Converter.stringToInt(memory.readWord(registers.PTR, memory.getAddress(x, y), Memory.Segment.Segment_Data));
    }

    public void SW(int x, int y) {
        if (!memory.isValidAddress(registers.PTR, x, y, Segment.Segment_Data)) {
            registers.PI = 1;
            return;
        }

        memory.writeWord(registers.PTR, memory.getAddress(x, y), Memory.Segment.Segment_Data, String.valueOf(registers.AR));
    }

    public void ATOB() {
        registers.BR = registers.AR;
    }

    public void BTOA() {
        registers.AR = registers.BR;
    }

    public void PUSH() {
        if (registers.SP == registers.SS + (memory.BLOCKS_PER_SS * memory.WORDS_PER_BLOCK))
            return;

        memory.writeWord(registers.PTR, registers.SP, Memory.Segment.Segment_Stack, String.valueOf(registers.AR));
        increaseSP();
    }

    public void POP() {
        if (registers.SP == registers.SS)
            return;

        decreaseSP();
        registers.AR = Integer.parseInt(memory.readWord(registers.PTR, registers.SP, Memory.Segment.Segment_Stack));

    }

    public void PRINTS() {
        System.out.println(Converter.intToString(registers.AR)); // / fix

    }

    public void PRINTW() {
        System.out.println(registers.AR);
    }

    public void SCANW() {

    }

    public ProcessesManager getProcessesManager() {
        return processesManager;
    }

    public ResourcesManager getResourcesManager() {
        return resourcesManager;
    }
    
    public abstract void doStep();

}

